#include "vida.h"

void cria_matriz(tabuleiro t, int linha, int coluna)
{
   int tam = linha*coluna*sizeof(int8);

   t->linha = linha;
   t->coluna = coluna;
   t->atual =(int8*) malloc(tam);
   t->anterior = (int8*)malloc(tam);

   memset(t->atual, tam, 0);
   memset(t->anterior, tam, 0);
}

int tabuleiro_xy(tabuleiro t, int posx, int posy)
{
   if (posx>=0 && posx<t->coluna && posy>=0 && posy<t->linha)
      return posx + (t->coluna)*posy;
   else
      return -1;
}

void tabuleiro_abs(tabuleiro t, int posabs, int *posx, int *posy)
{
   *posx = 0;
   *posy = 0;

   if (posabs >= 0 && posabs < t->linha*t->coluna)
   {
      *posx = posabs % t->coluna;
      *posy = (posabs - (*posx))/t->coluna;
   }  
}

void insere_vida(tabuleiro t, int posx, int posy)
{
   int posabs = tabuleiro_xy(t, posx, posy);

   if (posabs != -1)
      t->atual[posabs]=vivo;
}

void retira_vida(tabuleiro t, int posx, int posy)
{
  int posabs = tabuleiro_xy(t, posx, posy);

   if (posabs != -1)
      t->atual[posabs]=morto;
}

void libera_tabuleiro(tabuleiro t)
{
   free(t->atual);
   free(t->anterior);
   free(t);
}

void* atualiza_tabuleiro(void* parametro)
{
	int i;
	parametros *p = (parametros*) parametro;
	
	for(i = 0; i < p->num_blocos; i++)
		p->t->atual[p->pos_inicial + i] = testa_vida(p->t, p->pos_inicial + i);
}

estado testa_vida(tabuleiro t, int pos)
{
	int celulas_vivas = conta_celulas(t, pos); 
	int cond_vivo[] = REGRA_VIVO;
   int cond_renasce[] = REGRA_RENASCE;
   int i;
   
	/* Caso em que a celula esta viva */
	if(t->anterior[pos] == vivo)
	{
   	for (i = 1; i <= cond_vivo[0]; i++)
			if(celulas_vivas == cond_vivo[i])
         	return vivo;
	}
	/* Caso esteja morto */
	else
	{
		for (i = 1; i <= cond_renasce[0]; i++)
      	if(celulas_vivas == cond_renasce[i])
         	return vivo;		
	}
	return morto;
}

int conta_celulas(tabuleiro t, int pos)
{
	int num_celulas;
	int x, y, i;
	
	tabuleiro_abs(t, pos, &x, &y);
	
	num_celulas = 0;
	
	/* Verifica as celulas superiores*/
	for(i = 0; i < 3; i++)
	{
		/* Verifica se a posicao eh valida e se esta vivo */
		if(verifica_pos(t, x - 1 + i, y - 1) && 
			t->anterior[(pos - t->coluna) - 1 + i] == vivo)
			num_celulas++;
	}
	
	/* Verifica a celula na esquerda */
	if(verifica_pos(t, x - 1, y) && 
		t->anterior[pos - 1] == vivo)
		num_celulas++;
		
	/* Verifica a celula na direita */
	if(verifica_pos(t, x + 1, y) && 
		t->anterior[pos + 1] == vivo)
		num_celulas++;
	
	/* Verifica as celulas inferiores*/
	for(i = 0; i < 3; i++)
	{
		/* Verifica se a posicao eh valida e se esta vivo */
		if(verifica_pos(t, x - 1 + i, y + 1) && 
			t->anterior[(pos + t->coluna) - 1 + i] == vivo)
			num_celulas++;
	}
	
	return num_celulas;	
}

bool verifica_pos(tabuleiro t, int x, int y)
{
	if(x < 0 || y < 0 || x >= t->coluna || y >= t->linha)
		return false;
	
	return true;	
		
}

void calcula_proximo(tabuleiro t)
{
    int num_threads, i;
    pthread_t *threads;
    pthread_t thr_resto;
    parametros *p, p_resto;

    /* Calcula numero de threads necessarias */
    num_threads=(t->coluna*t->linha)/NUM_BLOCOS;
    
    threads=(pthread_t *)malloc(sizeof(pthread_t)*num_threads);
    p=(parametros *)malloc(sizeof(parametros)*num_threads);

    /* Cria as threads principais */
    for(i=0; i<num_threads; i++)
    {
        p[i].num_blocos=NUM_BLOCOS;
        p[i].pos_inicial=NUM_BLOCOS*i;
        p[i].t=t;
        while(pthread_create(&threads[i], NULL, atualiza_tabuleiro, (void *)&p[i]));
    }
        
    /* Designa uma thread para cuidar dos blocos restantes, se existirem */
    if((t->coluna*t->linha)%NUM_BLOCOS)
    {
        p_resto.num_blocos=t->coluna*t->linha - num_threads*NUM_BLOCOS;
        p_resto.pos_inicial=NUM_BLOCOS*num_threads;
        p_resto.t=t;
        pthread_create(&thr_resto, NULL, atualiza_tabuleiro, (void *)&p_resto);
    }

    /* Faz join em todas as threads criadas */
    for(i=0; i<num_threads; i++)
        pthread_join(threads[i], NULL);

    if((t->coluna*t->linha)%NUM_BLOCOS)
        pthread_join(thr_resto, NULL);

    free(threads);
    free(p);
}

void gera_rnd(tabuleiro t, int n)
{
   int i, tam, x, y;

   if (!n)
      return;
   
   tam = t->coluna*t->linha;
   for(i = 0; i < tam; i++)
   {
      if (!(rand() % n))
      {
         tabuleiro_abs(t, i, &x, &y);
         insere_vida(t, x, y);
      }
   }
}
